#include "Matrix.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

Matrix::Matrix()
{
    height = 0;
    width = 0;
    data = nullptr;
}

Matrix::Matrix(int h,int w)
{
    init(h,w);
}

Matrix::Matrix(int h, int w, Pixel pix)
{
    init(h,w);
    for(int i = 0;i < h;i++)
    {
        for(int j = 0;j < w;j++)
        {
            data[i][j] = pix;
        }
    }
}

Matrix::Matrix(const Matrix &m)
{
    init( m.height , m.width );
    for(int i = 0;i < height;i++)
    {
        for(int j = 0;j < width;j++)
        {
            data[i][j] = m.data[i][j];
        }
    }
}

Matrix::~Matrix()
{
    del();
}

void Matrix::ReadText(const char* filename)
{
    FILE *fp;
    fp = fopen(filename,"r");
    if(nullptr == fp)
    {
        printf("ReadText error");
        exit(0);
    }
    fscanf(fp,"%d %d",&height,&width);
    if(nullptr != data)
    {
        del();
    }
    init(height,width);

    for(int i = 0;i < height;i++)
    {
        for(int j = 0;j < width;j++)
        {
            fscanf(fp,"%hhu", &data[i][j].red);
            fscanf(fp,"%hhu", &data[i][j].gre);
            fscanf(fp,"%hhu", &data[i][j].blu);
        }
    }
    fclose(fp);
}

void Matrix::WriteText(const char* filename)
{
    FILE *fp;
    fp = fopen(filename,"w");
    fprintf(fp,"%d %d\n",height,width);
    for(int i = 0;i < height;i++)
    {
        for(int j = 0;j < width;j++)
        {
            fprintf(fp,"%hhu ",data[i][j].red);
            fprintf(fp,"%hhu ",data[i][j].gre);
            fprintf(fp,"%hhu ",data[i][j].blu);
        }
        fprintf(fp,"\n");
    }
    fclose(fp);
}

void Matrix::Zeros(int h, int w)
{
    if(nullptr != data)
    {
        del();
    }
    init(h , w);
    for(int i = 0;i < height;i++)
    {
        for(int j = 0;j < width;j++)
        {
            data[i][j] = {0, 0, 0};
        }
    }
}

void Matrix::Ones(int h, int w)
{
    if(nullptr != data)
    {
        del();
    }
    init(h , w);
    for(int i = 0;i < height;i++)
    {
        for(int j = 0;j < width;j++)
        {
            data[i][j] = {1, 1, 1};
        }
    }
}


void Matrix::Identity(int n)
{
    if(nullptr != data)
    {
        del();
    }
    init(n, n);
    for(int i = 0;i < height;i++)
    {
        for(int j = 0;j < width;j++)
        {
            data[i][j] = {0, 0, 0};
        }
        data[i][i] = {1, 1, 1};
    }
}

int Matrix::Height() const
{
    return height;
}

int Matrix::Width() const
{
    return width;
}

Matrix Matrix::MajorDiagonal()
{
    if(height != width)
    {
        printf("MajorDiagonal error");
        exit(0);
    }

    Matrix temp(height,1);
    for(int i = 0;i < height;i++)
    {
        temp.data[i][0] = data[i][i];
    }

    return temp;
}

Matrix Matrix::MinorDiagonal()
{
    if(height != width)
    {
        printf("MinorDiagonal error");
        exit(0);
    }

    Matrix temp(height,1);
    for(int i = 0;i < height;i++)
    {
        temp.data[i][0] = data[i][width - i - 1];
    }

    return temp;
}

Matrix Matrix::Row(int n)
{
    Matrix temp(1,width);

    for(int i = 0;i < width;i++)
    {
        temp.data[0][i] = data[n][i];
    }

    return temp;
}

Matrix Matrix::Column(int n)
{
    Matrix temp(height,1);
    for(int i = 0;i < height;i++)
    {
        temp.data[0][i] = data[i][n];
    }

    return temp;
}

void Matrix::Transpose()
{
    Matrix temp((*this));
    del();
    init(temp.width, temp.height);
    for(int i = 0;i < height;i++)
    {
        for(int j = 0;j < width; j++)
        {
            data[i][j] = temp.data[j][i];
        }
    }
}

Pixel & Matrix::At(int row, int col)
{
    return data[row][col];
}

Pixel Matrix::At(int row, int col) const
{
    return data[row][col];
}

void Matrix::Set(int row, int col, Pixel pix)
{
    data[row][col] = pix;
}

void Matrix::Set(Pixel pix)
{
    for(int i = 0;i < height;i++)
    {
        for(int j = 0;j < width;j++)
        {
            data[i][j] = pix;
        }
    }
}

void Matrix::Reshape(int h, int w)
{
    if(h * w != height * width)
    {
        printf("Reshape erro");
        exit(0);
    }
    if(height == h)
    {
        return;
    }
    Matrix temp(1,height * width);

    for(int i = 0;i < height;i++)
    {
        for(int j = 0;j < width;j++)
        {
            temp.data[0][i * width + j] = data[i][j];
        }
    }
    del();
    init(h, w);
    for(int i = 0;i < h;i++)
    {
        for(int j = 0;j < w;j++)
        {
            data[i][j] = temp.data[0][i * w + j];
        }
    }
}

bool Matrix::IsEmpty()
{
    return (data == nullptr);
}

bool Matrix::IsSquare()
{
    return (height == width);
}

void Matrix::CopyTo(Matrix &m)
{
    if(&m == this)
    {
        return;
    }
    m.del();
    m.init(height, width);
    for(int i = 0;i < height;i++)
    {
        for(int j = 0;j < width;j++)
        {
            m.data[i][j] = data[i][j];
        }
    }
}

void Matrix::Cat(Matrix &m, int code)
{
    Matrix temp;
    if(1 == code || 2 == code)
    {
        if(width > m.width)
        {
            temp.Zeros(height + m.height, width);
        }
        else
        {
            temp.Zeros(height + m.height, m.width);
        }

        if(1 == code)
        {
            for(int i = 0;i < m.height;i++)
            {
                for(int j = 0;j < m.width;j++)
                {
                    temp.data[i][j] = m.data[i][j];
                }
            }
            for(int i = 0;i < height;i++)
            {
                for(int j = 0;j < width;j++)
                {
                    temp.data[i + m.height][j] = data[i][j];
                }
            }
        }
        if(2 == code)
        {
            for(int i = 0;i < height;i++)
            {
                for(int j = 0;j < width;j++)
                {
                    temp.data[i][j] = data[i][j];
                }
            }
            for(int i = 0;i < m.height;i++)
            {
                for(int j = 0;j < m.width;j++)
                {
                    temp.data[i + height][j] = m.data[i][j];
                }
            }
        }
    }

    else if(3 == code || 4 == code)
    {
        int w = width + m.width;
        if(height > m.height)
        {
            temp.Zeros(height, w);
        }
        else
        {
            temp.Zeros(m.height,w);
        }

        if(3 == code)
        {
            for(int i = 0;i < m.height;i++)
            {
                for(int j = 0;j < m.width;j++)
                {
                    temp.data[i][j] = m.data[i][j];
                }
            }
            for(int i = 0;i < height;i++)
            {
                for(int j = 0;j < width;j++)
                {
                    temp.data[i][j + m.width] = data[i][j];
                }
            }
        }
        if(4 == code)
        {
            for(int i = 0;i < height;i++)
            {
                for(int j = 0;j < width;j++)
                {
                    temp.data[i][j] = data[i][j];
                }
            }
            for(int i = 0;i < m.height;i++)
            {
                for(int j = 0;j < m.width;j++)
                {
                    temp.data[i][j + width] = m.data[i][j];
                }
            }
        }
    }
    else
    {
        printf("Cat error");
    }

    temp.CopyTo((*this));
}

void Swap(Matrix &a, Matrix &b)
{
    Matrix temp(a);
    b.CopyTo(a);
    temp.CopyTo(b);
}

Matrix& Matrix::operator=(const Matrix &m)
{
    if(&m == this)
    {
        return (*this);
    }
    if(data != nullptr)
    {
        del();
    }
    init(m.height, m.width);
    for(int i = 0;i < height;i++)
    {
        for(int j = 0;j < width;j++)
        {
            data[i][j] = m.data[i][j];
        }
    }

    return (*this);
}

Matrix& Matrix::operator=(Pixel pix)
{
    for(int i = 0;i < height;i++)
    {
        for(int j = 0;j < width;j++)
        {
            data[i][j] = pix;
        }
    }
    return (*this);
}

bool Matrix::operator==(const Matrix &m)
{
    if(&m == this)
    {
        return true;
    }
    if(m.height == height && m.width == width)
    {
        for(int i =0 ;i < height;i++)
        {
            for(int j = 0;j < width;j++)
            {
                if(m.data[i][j].red != data[i][j].red || m.data[i][j].gre != data[i][j].gre || m.data[i][j].blu != data[i][j].blu)
                {
                    return false;
                }
            }
        }
        return true;
    }
    return false;
}

Matrix& Matrix::operator++()
{
    for(int i = 0;i < height;i++)
    {
        for(int j = 0;j < width;j++)
        {
            data[i][j].red += 1;
            data[i][j].gre += 1;
            data[i][j].blu += 1;
        }
    }
    return (*this);
}

Matrix& Matrix::operator--()
{
    for(int i = 0;i < height;i++)
    {
        for(int j = 0;j < width;j++)
        {
            data[i][j].red -= 1;
            data[i][j].gre -= 1;
            data[i][j].blu -= 1;
        }
    }
    return (*this);
}

Matrix Matrix::operator++(int)
{
    Matrix temp(*this);
    for(int i = 0;i < height; i++)
    {
        for(int j = 0;j < width;j++)
        {
            data[i][j].red += 1;
            data[i][j].gre += 1;
            data[i][j].blu += 1;
        }
    }
    return temp;
}

Matrix Matrix::operator--(int)
{
    Matrix temp(*this);
    for(int i = 0;i < height; i++)
    {
        for(int j = 0;j < width;j++)
        {
            data[i][j].red -= 1;
            data[i][j].gre -= 1;
            data[i][j].blu -= 1;
        }
    }
    return temp;
}

void Matrix::init(long h,long w)
{
    height = h;
    width = w;
    if(0 == h)
    {
        data = nullptr;
        return;
    }
    data = new Pixel*[height];
    for(int i  = 0;i < height; i++)
    {
        data[i] = new Pixel[width];
    }
}



void Matrix::del()
{
    for(int i = 0;i < height;i++)
    {
        delete[] data[i];
    }
    delete[] data;
    height = 0;
    width = 0;
    data = nullptr;
}
